﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Collections;
using System.Threading.Tasks;
using System.Threading;
using ProgNetComponents.Classes;

namespace ProgNetComponentsDx.Chat
{
    public class ChatServer
    {
        public int Port { get; private set; }
        private TcpListener listener;
        public UserHandler UserConnected;
        public UserHandler UserDisConnected;
        protected void FireUserConnected(string nick)
        {
            if (UserConnected != null)
                UserConnected(nick);
        }

        protected void FireUserDisConnected(string nick)
        {
            if (UserDisConnected != null)
                UserDisConnected(nick);
        }

        private Dictionary<string, TcpClient> nickName = new Dictionary<string, TcpClient>();
        protected Dictionary<string, TcpClient> UsersByNick
        {
            get { return nickName; }
        }
        private Dictionary<TcpClient, string> nickNameByConnect = new Dictionary<TcpClient, string>();
        protected Dictionary<TcpClient, string> UsersByTcpClient
        {
            get { return nickNameByConnect; }
        }
        private List<string> users = new List<string>();
        public List<string> Users { get { return users; } }

        public event ChatMessageHandler MessageReceived;
        
        protected void FireMessageReceived(string from, ChatMessageInfo info)
        {
            if (MessageReceived != null)
                MessageReceived(from, new ChatMessageEventArgs(info));
        }
        protected virtual void OnMessageReceived(string from, ChatMessageInfo info)
        {
            FireMessageReceived(from, info);
            switch (info.Type)
            {
                case ChatMessages.UserDisconnected:
                    if (nickName.ContainsKey(from))
                        OnUserDisconnected(from, nickName[from]);
                    break;
                case ChatMessages.Beep:
                    break;
                default:
                    string to = info.User;
                    if (to.IsNotNull())
                    {
                        if (nickName.ContainsKey(to))
                        {
                            info.User = from;
                            nickName[to].GetStream().SendMessage(info);
                        }
                        else
                        {
                            info.Type = ChatMessages.UserDisconnected;
                            info.Message = to;
                            nickName[from].GetStream().SendMessage(info);
                        }
                    }
                    break;
            }
        }
        protected virtual void SendMessageToAll(ChatMessageInfo info)
        {
            try
            {
                var keys = UsersByNick.Keys.ToArray();
                foreach (var kvp in keys)
                {
                    SendMessage(kvp, info);
                }
            }
            catch { }
        }
        protected virtual void SendMessage(string user, ChatMessageInfo info)
        {
            try
            {
                if (UsersByNick.ContainsKey(user))
                {
                    UsersByNick[user].GetStream().SendMessage(info);
                }
            }
            catch { }
        }
        protected virtual void SendMessage(string user, string from, string msg)
        {
            ChatMessageInfo info = new ChatMessageInfo();
            info.Message = msg;
            info.Type = ChatMessages.StringMsg;
            info.User = from;
            SendMessage(user, info);
        }
        public ChatServer(int port)
        {
            this.Port = port;
        }

        public virtual void Start()
        {
            if (listener == null)
            {
                listener = new TcpListener(new System.Net.IPEndPoint(System.Net.IPAddress.Any, Port));
                Task t = Task.Factory.StartNew(Run);
            }
        }
        public void Stop()
        {
            try
            {
                listener.Stop();
                listener.Server.Close();
            }
            catch { }
            finally
            {
                listener = null;
                nickName.Clear();
                nickNameByConnect.Clear();
                users.Clear();
            }
        }
        protected virtual void Run()
        {
            try
            {
                listener.Start();
                while (true)
                {
                    TcpClient client = listener.AcceptTcpClient();
                    Task t = Task.Factory.StartNew(OnClientConnected, client);
                }
            }
            catch { }
        }
        protected virtual void OnUserConnected(string nick, TcpClient client)
        {
            FireUserConnected(nick);
            users.Add(nick);
            var otherUsers = nickName.Values.ToArray();

            nickName.Add(nick, client);
            nickNameByConnect.Add(client, nick);
            ChatMessageInfo info = new ChatMessageInfo();

            info.User = nick;
            info.Type = ChatMessages.UserConnected;
            
            foreach (var x in otherUsers)
            {
                x.GetStream().SendMessage(info);
            }

        }
        private  void OnClientConnected(object client)
        {
            OnClientConnected(client as TcpClient);
        }
        protected virtual void OnUserDisconnected(string nick, TcpClient client)
        {
            if (users.Contains(nick))
            {
                FireUserDisConnected(nick);
                users.Remove(nick);
                nickName.Remove(nick);
                nickNameByConnect.Remove(client);
                ChatMessageInfo info = new ChatMessageInfo();

                info.User = nick;
                info.Type = ChatMessages.UserDisconnected;
                var otherUsers = nickName.Values.ToArray();
                foreach (var x in otherUsers)
                    x.GetStream().SendMessage(info);
            }
        }
        protected virtual void OnClientConnected(TcpClient client)
        {
            if (client != null)
            {
                using (NetworkStream networkStream = client.GetStream())
                {
                    ChatMessageInfo info = new ChatMessageInfo();
                    info.Type = ChatMessages.GetNick;
                    info.User = string.Empty;
                    info.Message = string.Empty;
                    ObjectHolder<bool> exited = new ObjectHolder<bool>();

                    while (!exited.Value && (info.Message.IsNull() || nickName.ContainsKey(info.Message)))
                    {
                        networkStream.SendMessage(info);
                        networkStream.ReceiveMessage(exited, info);
                    }
                    if (!exited.Value)
                    {
                        string nick = info.Message;
                        OnUserConnected(nick, client);

                        object[] tab = new object[]{client, exited};
                        ObjectHolder<TcpClient, ObjectHolder<bool>> obj = new ObjectHolder<TcpClient, ObjectHolder<bool>>();
                        obj.Value1 = client;
                        obj.Value2 = exited;

                        Task tChecConnected = Task.Factory.StartNew(CheckConnection, obj);

                        while (!exited.Value)
                        {
                            try
                            {
                                ChatMessageInfo info2 = new ChatMessageInfo();
                                networkStream.ReceiveMessage(exited, info2);
                                if (exited.Value)
                                    break;
                                OnMessageReceived(nick, info2);
                            }
                            catch { break; }
                        }
                        OnUserDisconnected(nick, client);
                    }
                }
            }
        }

        protected virtual bool ShouldSendBeep()
        {
            return true;
        }

        private void CheckConnection(object obj)
        {
            ObjectHolder<TcpClient, ObjectHolder<bool>> o = obj as ObjectHolder<TcpClient, ObjectHolder<bool>>;
            if (o != null)
            {
                int i = 0;
                ChatMessageInfo info = new ChatMessageInfo();
                info.Message = string.Empty;
                info.Type = ChatMessages.Beep;

                while (!o.Value2.Value)
                {
                    System.Threading.Thread.Sleep(50);

                    if (!o.Value1.Connected)
                    {
                        o.Value2.Value = true;
                    }
                    else
                    {
                        i++;
                        if (i == 20)
                        {
                            i = 0;
                            try
                            {
                                if (ShouldSendBeep())
                                {
                                    o.Value1.GetStream().SendMessage(info);
                                }
                            }
                            catch
                            {
                                o.Value2.Value = true;
                            }
                        }
                    }
                }
                if (nickNameByConnect.ContainsKey(o.Value1))
                {
                    string nick = nickNameByConnect[o.Value1];
                    if (users.Contains(nick))
                    {
                        FireUserDisConnected(nick);
                        users.Remove(nick);
                        nickName.Remove(nick);
                        nickNameByConnect.Remove(o.Value1);
                    }
                }
            }
        }
    }

    public delegate void UserHandler(string user);
}
